#include "camer.hpp"

Camer::Camer(const char *videoX)
{
    //打开摄像头
    fd = open(videoX, O_RDWR);
    if (fd < 0)
    {
        perror("打开设备失败");
    }
    printf("打开设备 : %s 成功\n",videoX);

    //初始化摄像头参数
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    vfmt.fmt.pix.width = 640;
    vfmt.fmt.pix.height = 480;
    //图片编码格式
    // V4L2_PIX_FMT_RGB24   :   24位 RGB 格式，每个像素由 3 个字节表示，顺序为 R、G、B。
    // V4L2_PIX_FMT_BGR24   :   24位 BGR 格式，每个像素由 3 个字节表示，顺序为 B、G、R。
    // V4L2_PIX_FMT_YUV420  :   YUV420 格式，包含 Y、U、V 三个分量，通常用于视频压缩。
    // V4L2_PIX_FMT_NV12    :   NV12 格式，包含一个亮度平面（Y）和一个色度平面（UV），常用于 H.264 编码。
    // V4L2_PIX_FMT_MJPEG   :   Motion-JPEG 格式，一种基于 JPEG 的视频压缩格式。
    // V4L2_PIX_FMT_H264    :   H.264 格式，一种常见的视频编码格式。
    // V4L2_PIX_FMT_YUYV    :   YUYV 格式，每两像素共用一组 UV 信息，用于捕获和显示视频。
    // V4L2_PIX_FMT_UYVY    :   UYVY 格式，与 YUYV 类似，但 UV 顺序不同。

    vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    if( ! set_camer_parameter(vfmt) ) exit(1) ;
    if( ! apply_for_kernel_space()  ) exit(1) ;

    // 开启采集
    turn_on_acquisition();

}

// 开始采集
bool Camer::turn_on_acquisition()
{
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	ret = ioctl(fd, VIDIOC_STREAMON, &type);
	if(ret < 0)
	{
		perror("开启失败");
        return false;
	}
    printf("开始采集\n");
    return true;
}

// 结束采集
bool Camer::end_the_acquisition()
{
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
    printf("停止采集");
    return true;
}


Camer::~Camer()
{
    // 停止采集
    end_the_acquisition();

    // 释放映射
    for(int i=0; i<4; i) munmap(mptr[i], size[i]) ;
    printf("释放映射");

    // 关闭设备
    close(fd);
    printf("关闭设备");
}

// 设置摄像头参数
bool Camer::set_camer_parameter(struct v4l2_format & vfmt_p)
{
    ret = ioctl(fd, VIDIOC_S_FMT, &vfmt_p);
    if (ret < 0)
    {
        perror("设置格式失败");
        return false;
    }
    return true;
}

// 申请内核空间并映射
bool Camer::apply_for_kernel_space()
{
    reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuffer.count = REQBUFFER_COUNT; //申请个缓冲区数量
    reqbuffer.memory = V4L2_MEMORY_MMAP; //映射方式
    if (ioctl(fd, VIDIOC_REQBUFS, &reqbuffer) < 0)
    {
        perror("申请空间失败");
        exit(1);
    }
    printf("申请内核空间成功 缓冲器数量为 %d\n",REQBUFFER_COUNT);

    //初始化type, index
    mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    for(int i=0; i<REQBUFFER_COUNT; i++)
	{
        mapbuffer.index = i;
		ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);//从内核空间中查询一个空间做映射
		if(ret < 0)
		{
			perror("查询内核空间队列失败");
		}
		mptr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, 
                                            MAP_SHARED, fd, mapbuffer.m.offset);
        size[i] = mapbuffer.length;

		//通知使用完毕--‘放回去’
		ret  = ioctl(fd, VIDIOC_QBUF, &mapbuffer);
		if(ret < 0)
		{
			perror("放回失败");
		}
    }
    return true;
}

// 从队列中提取数据
bool Camer::extract_frame_from_queue()
{
    mtx.lock(); // 互斥锁
    // 从队列中提取一帧数据
	readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
	if(ret < 0)
	{
		perror("提取数据失败");
        return false;
	}
    return true;
}

bool Camer::put_it_back_queue()
{
    // 通知内核已经使用完毕
	ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
    mtx.unlock(); // 解锁
	if(ret < 0)
	{
		perror("放回队列失败");
        return false;
	}
    return true;
}

bool Camer::refresh_frame_address(frame_uchar & container)
{
    container.data    = mptr[readbuffer.index];
    container.length  = readbuffer.length;
    return true;
}

bool Camer::get_rawdata_and_wait()
{
    // 拿取队列中的图片并等待处理
    if( ! extract_frame_from_queue() ) return false;
    refresh_frame_address(frame); 
    return true;
}

bool Camer::get_camer_and_save(std::string name)
{
    get_rawdata_and_wait();
    // 保存
    FILE *file=fopen(name.c_str(), "w+");
    fwrite(frame.data, frame.length, 1, file); //fwrite(mptr[readbuffer.index], readbuffer.length, 1, file);
	fclose(file);
    // 放回队列
	put_it_back_queue(); 
    return true;
}

// 将图片存在opencv的Mat对象中
bool Camer::get_frame_encode_to_opencv(cv::Mat& image)
{
    get_rawdata_and_wait();
    // 保存
    cv::Mat mjpeg_mat(1, frame.length, CV_8UC1, frame.data); // 创建矩阵
    image = cv::imdecode(mjpeg_mat, cv::IMREAD_COLOR);       // 将矩阵解码成图像
    // 放回队列
    put_it_back_queue();
    return true;
}